#!/bin/bash
# Script which backups metadata into a VDI
# Citrix Systems Inc, 2008

trap "cleanup" TERM INT

if [ ! -e @INVENTORY@ ]; then
  echo Must run on a Citrix Hypervisor host.
  exit 1
fi

. @INVENTORY@

XE="@BINDIR@/xe"

master_uuid=$(${XE} pool-list params=master --minimal)
if [ $? -gt 0 ]; then
   echo Error: unable to determine master host uuid
   exit 1
fi

if [ "${master_uuid}" != "${INSTALLATION_UUID}" ]; then
   echo Error: must run this script on the master host in the resource pool.
   exit 1
fi

history_kept=25
metadata_version=1
debug=/bin/true

function usage {
    echo "Usage: $0 [-h] [-i] [-u <SR UUID>]"
    echo
    echo " -h: Display this help message"
    echo " -c: Create a backup VDI if one cannot be found (default: false)"
    echo " -i: Initialize the backup VDI before performing the backup"
    echo " -u: UUID of the SR you wish to backup to"
    echo " -d: Dont perform a backup, but leave the disk mounted in a shell"
    echo " -k: Number of older backups to preserve (default: ${history_kept})"
    echo " -n: Just try to find a backup VDI and stop the script after that"
    echo " -f  Force backup even when less than 10% free capacity is left on the backup VDI"
    echo " -v: Verbose output"
    echo 
    echo
    echo "Use the -d option to examine the backup VDI, and restore it if necessary."
    echo "It is a good idea to copy the backup onto the control domain before"
    echo "restoring it via the xe pool-database-restore CLI command."
    exit 1
}

function test_sr {
  sr_uuid_found=$(${XE} sr-list uuid="$1" --minimal)
  if [ "${sr_uuid_found}" != "$1" ]; then
     echo Invalid SR UUID specified: $1
     usage
  fi
}

leave_mounted=0
init_fs=0
create_vdi=0 
just_find_vdi=0
fs_uninitialised=0
usage_alert=90
force_backup=0
while getopts "hvink:u:dcf" opt ; do
    case $opt in
    h) usage ;;
    c) create_vdi=1 ; fs_uninitialised=1 ;;
    i) init_fs=1 ; fs_uninitialised=1 ;;
    k) history_kept=${OPTARG} ;;
    u) sr_uuid=${OPTARG} ;;
    d) leave_mounted=1 ;;
    n) just_find_vdi=1 ;;
    v) debug="" ;;
    f) force_backup=1 ;;
    *) echo "Invalid option"; usage ;;
    esac
done

# get pool uuid
IFS=,
pool_uuid=$(${XE} pool-list params=uuid --minimal)
if [ -z "${pool_uuid}" ]; then
  echo Unable to determine pool UUID.
  exit 1
fi

# determine if the SR UUID is vaid
if [  -z "${sr_uuid}" ]; then
  # use the default-SR from the pool
  sr_uuid=$(${XE} pool-param-get uuid=${pool_uuid} param-name=default-SR)
fi
test_sr "${sr_uuid}"

sr_name=$(${XE} sr-param-get uuid=${sr_uuid} param-name=name-label)
# see if a backup VDI already exists on the selected SR
vdi_uuid=$(${XE} vdi-list other-config:ctxs-pool-backup=true sr-uuid=${sr_uuid} params=uuid --minimal)

mnt=
function cleanup {
   trap "" TERM INT
   cd /
   if [ ! -z "${mnt}" ]; then
      umount ${mnt} >/dev/null 2>&1
      rmdir ${mnt}
   fi

   if [ ! -z "${vbd_uuid}" ]; then
      ${debug} echo -n "Unplugging VBD: "
      ${XE} vbd-unplug uuid=${vbd_uuid} timeout=20
      # poll for the device to go away if we know its name
      if [ "${device}" != "" ]; then
          device_gone=0
          for ((i=0; i<10; i++)); do
              ${debug} echo -n "."
              if [ ! -b ${device} ]; then
                  ${debug} echo " done"
                  device_gone=1
                  break
              fi
              sleep 1
          done
          if [ ${device_gone} -eq 0 ]; then
              ${debug} echo " failed"
              echo Please destroy VBD ${vbd_uuid} manually.
          else
              ${XE} vbd-destroy uuid=${vbd_uuid}
          fi
      fi
   fi
   if [ ${fs_uninitialised} -eq 1 -a -n "${vdi_uuid}" ] ; then
      ${XE} vdi-destroy uuid=${vdi_uuid}
   fi
}

echo Using SR: ${sr_name}
if [ -z "${vdi_uuid}" ]; then
  if [ "${create_vdi}" -gt 0 ]; then
    echo -n "Creating new backup VDI: "
    vdi_uuid=$(${XE} vdi-create virtual-size=500MiB sr-uuid=${sr_uuid} type=user name-label="Pool Metadata Backup")
    init_fs=1
    if [ $? -ne 0 ]; then
       echo failed
       exit 1
    fi
  else
    echo "Backup VDI not found, aborting.  You can initialise one using the '-c' flag."
    exit 3
  fi
  echo ${vdi_uuid}
  ${XE} vdi-param-set uuid=${vdi_uuid} other-config:ctxs-pool-backup=true
else
  ${debug} echo "Using existing backup VDI: ${vdi_uuid}"
  fs_uninitialised=0
fi

if [ ${just_find_vdi} -gt 0 ]; then
  exit 0
fi

${debug} echo -n "Creating VBD: "
vbd_uuid=$(${XE} vbd-create vm-uuid=${CONTROL_DOMAIN_UUID} vdi-uuid=${vdi_uuid} device=autodetect)
${debug} echo ${vbd_uuid}


if [ $? -ne 0 -o -z "${vbd_uuid}" ]; then
  echo error creating VBD
  cleanup
  exit 1
fi

${debug} echo -n "Plugging VBD: "
${XE} vbd-plug uuid=${vbd_uuid}
device=/dev/$(${XE} vbd-param-get uuid=${vbd_uuid} param-name=device)

if [ ! -b ${device} ]; then
  ${debug} echo ${device}: not a block special
  cleanup
  exit 1
fi

${debug} echo ${device}

if [ $init_fs -eq 1 ]; then
  ${debug} echo -n "Creating filesystem: "
  mkfs.ext3 -j -F ${device} > /dev/null 2>&1
  ${debug} echo "done"
  fs_uninitialised=0
fi

${debug} echo -n "Mounting filesystem: "
mnt=/var/run/pool-backup-${vdi_uuid}
mkdir -p ${mnt}

/sbin/fsck -a ${device} >/dev/null 2>&1
if [ $? -ne 0 ]; then
  ${debug} fsck failed.  Please correct manually
  cleanup 
  exit 1
fi

mount ${device} ${mnt} > /dev/null 2>&1
if [ $? -ne 0 ]; then
  ${debug} echo failed
  cleanup
  exit 1
fi
${debug} echo ${mnt}

if [ ${leave_mounted} -eq 0 ]; then
  lrconf=${mnt}/conf/${vdi_uuid}
  if [ ! -f ${lrconf} ]; then
     ${debug} echo -n "Initialising rotation: "
     mkdir -p ${mnt}/conf/ 
     echo "${mnt}/${pool_uuid}.db {" >> ${lrconf}
     echo "    rotate ${history_kept}" >> ${lrconf}
     echo "    missingok" >> ${lrconf}
     echo "}" >> ${lrconf}
     echo done
     echo ${metadata_version} >> ${mnt}/.ctxs-metadata-backup
  fi

  # check the usage of the backup VDI
  usage=`cd ${mnt} && df . | sed -n "2p" | awk '{ print $5 }' | tr -d '%'`
  echo "Checking backup VDI space usage: $usage%"
  if [ $usage -gt $usage_alert ] && [ ${force_backup} -eq 0 ]; then
    echo "Running out of space, you can use "-d" option to attach VDI and free more space, exit now."
    cleanup
    exit 1
  fi

  # invoke logrotate to rotate over old pool db backups
  echo -n "Rotating old backups: "
  logrotate -f ${lrconf}
  num_found=$(find ${mnt} -name \*.db\.* | wc -l)
  echo found ${num_found}
  
  # perform the pool database dump
  echo -n "Backing up pool database: "
  ${XE} pool-dump-database file-name=${mnt}/${pool_uuid}.db
  echo done

  # backup the VM metadata for each VM in the pool into a dated directory
  datetime=$(date +%F-%H-%M-%S)
  metadir=${mnt}/metadata/${datetime}
  mkdir -p ${metadir}
  echo -n "Cleaning old VM metadata: "
  IFS=" "
  todelete=$(cd ${mnt}/metadata && ls -1 |sort -n | head -n -${history_kept} | xargs echo)
  for dir in ${todelete}; do
    rm -rf ${mnt}/metadata/${dir}
  done
  echo done
  IFS=","
  echo -n "Backing up SR metadata: "
  mkdir -p ${metadir}
  "@LIBEXECDIR@/backup-sr-metadata.py" -f ${metadir}/SRMETA.xml
  echo "done"

  echo -n "Backing up VM metadata: "
  ${debug} echo ""
  mkdir -p ${metadir}/all
  for vmuuid in $(${XE} vm-list params=uuid is-control-domain=false --minimal); do
    ${debug} echo -n .
    ${XE} vm-export --metadata uuid=${vmuuid} filename=${metadir}/all/${vmuuid}.vmmeta >/dev/null 2>&1
  done
  echo "done"
  echo -n "Backing up Template metadata: "
  ${debug} echo ""
  template_uuids=$("@LIBEXECDIR@/print-custom-templates")
  if [ $? -eq 0 ]; then
      for tmpl_uuid in ${template_uuids}; do
          ${XE} template-export --metadata template-uuid=${tmpl_uuid} filename=${metadir}/all/${tmpl_uuid}.vmmeta >/dev/null 2>&1
      done
  fi
  echo "done"
  "@LIBEXECDIR@/link-vms-by-sr.py" -d ${metadir}
else
  cd ${mnt}
  env PS1="Mounted backup VDI on: ${mnt}\nPress ^D to exit shell and safely detach it.\n\n[\u@\h \W]\$ " bash
fi

cleanup
